using System;
using System.Drawing;
using System.Windows.Forms;
using svs.Mobile.BeyondMedia.UiCore.GDI;

namespace svs.Mobile.BeyondMedia.UiCore.GDI
{
	public class Gdi : IDisposable
	{
		private IntPtr		m_hdc;
		private IntPtr		m_hwnd;
		private bool		m_fontChanged = false;
		private IntPtr		m_oldFont;
		private Color		m_textColor = Color.Black;
		private IntPtr		m_oldPen;
		private PenGdi		m_pen;
		private IntPtr		m_oldBrush;
		private FontGdi		m_font;
		private BrushGdi	m_brush = new BrushGdi(IntPtr.Zero);
		private bool		m_ownDc = true;			// True means we'll release DC in desctructor
		private Win32.TextAlign m_TextAlign = Win32.TextAlign.UNKNOWN;

		private Gdi()
		{
		}

		//
		// Gdi Constructor
		//
		public Gdi(IntPtr hwnd)
		{
			Init(hwnd, Rectangle.Empty);
		}

		public Gdi(IntPtr hwnd, Rectangle clipRect)
		{
			Init(hwnd, clipRect);
		}

		public Gdi(Control control)
		{
			control.Capture = true;
			IntPtr hwnd = Win32.GetHwnd(control);
			control.Capture = false;
			Init(hwnd, Rectangle.Empty);
		}

		public static Gdi FromHdc(IntPtr hdc, Rectangle clipRect)
		{
			Gdi gdi = new Gdi();
			gdi.InitWithHdc(hdc, clipRect, false);
			return gdi;
		}

		public static int GetScreenDpi(IntPtr hWnd)
		{
			using(Gdi g = new Gdi(hWnd))
			{
				return g.GetDpi();
			}
		}

		//
		// Init
		//
		private void Init(IntPtr hwnd, Rectangle clipRect)
		{
			m_hwnd = hwnd;
			InitWithHdc(Win32.GetDC(hwnd), clipRect, true);
		}

		private void InitWithHdc(IntPtr hdc, Rectangle clipRect, bool ownDc)
		{
			m_hdc = hdc;
			this.Transparent = true;				// Use transparent drawing by default
			m_ownDc = ownDc;

			if (!clipRect.IsEmpty)					// If there is a clip rectangle, us it.
			{
				int l, t, r, b;
				l = Convert.ToInt16(clipRect.Left);
				t = Convert.ToInt16(clipRect.Top);
				r = Convert.ToInt16(clipRect.Right);
				b = Convert.ToInt16(clipRect.Bottom);
				IntPtr hrgn = Win32.CreateRectRgn(l, t, r, b);
				int i = Win32.SelectClipRgn(m_hdc, hrgn);
				Win32.DeleteObject(hrgn);
			}
		}

		//
		// Gdi Destructor
		//
		~Gdi()
		{
			Release();								// Make sure we release the DC
		}

		public int GetDpi()
		{
			return Win32.GetDeviceCaps(m_hdc, (int)Win32.LogPixels.LOGPIXELSX);
		}

		//
		// ClipRegion
		//
		public IntPtr ClipRegion
		{
			set { Win32.SelectClipRgn(m_hdc, value); }
		}

		//
		// Dispose
		//
		public void Dispose()
		{
			Release();								// Release the DC
			GC.SuppressFinalize(this);				// Don't call the destructor
		}

		//
		// BackColor
		//
		public Color BackColor
		{
			set { Win32.SetBkColor(m_hdc, ToGdiColor(value)); }
			get { return Color.FromArgb(Win32.GetBkColor(m_hdc)); }
		}

		//
		// Brush Property
		//
		public BrushGdi Brush
		{
			set
			{
				if ((IntPtr) value == IntPtr.Zero)
				{
					if (m_oldBrush != IntPtr.Zero)
						Win32.SelectObject(m_hdc, m_oldBrush);
					m_oldBrush = IntPtr.Zero;
				}
				else
				{
					IntPtr br = Win32.SelectObject(m_hdc, (IntPtr) value);
					if (m_oldBrush == IntPtr.Zero)
						m_oldBrush = br;
				}
				m_brush = value;
			}
			get { return m_brush; }
		}

		//
		// CreatePen
		//
		public static PenGdi CreatePen(Color c)
		{
			return new PenGdi(Win32.CreatePen(0, 1, Win32.Color2ColorRef(c)));
		}

		public static PenGdi CreatePen(Color color, PenMode mode)
		{
			return new PenGdi(Win32.CreatePen((int) mode, 1, ToGdiColor(color)));
		}

		//
		// ToGdiColor
		//
		public static int ToGdiColor(Color color)
		{
			return (color.B << 16) + (color.G << 8) + color.R;
		}

		//
		// CreateSolidBrush
		//
		public static BrushGdi CreateSolidBrush(Color c)
		{
			return new BrushGdi(Win32.CreateSolidBrush(Win32.Color2ColorRef(c)));
		}

		//
		// DeleteObject
		//
		public bool DeleteObject(IntPtr obj)
		{
			return Win32.DeleteObject(obj);
		}

		//
		// DrawClipped
		//
		// Draws the text. If the text is wider than the width you provide, we'll show
		// as much of the text as possible with "..." at the very end.
		//
		public void DrawClipped(string text, int width, int x, int y)
		{
			int[] extents;
			int maxChars;
			Size size = GetTextExtent(text, width, out extents, out maxChars);

			if (maxChars == text.Length)
				ExtTextOut(x, y, text);
			else
			{
				int xEllipse = width - GetTextExtent("...").Width;
				int numChars;
				for (numChars = maxChars; numChars > 0; numChars--)
				{
					if (extents[numChars - 1] <= xEllipse)
					{
						xEllipse = extents[numChars - 1];
						break;
					}
				}
				ExtTextOut(x, y, text.Substring(0, numChars));
				ExtTextOut(x + xEllipse, y, "...");
			}
		}

		//
		// DrawText
		//
		public int DrawText(string text, Win32.RECT rect, Win32.DT format)
		{
			if (text != null)
				return Win32.DrawText(m_hdc, text, text.Length, ref rect, format);
			else
				return 0;
		}

		//
		// ExtTextOut
		//
		// Draws a single line of text into the window, without clipping or wrapping.
		//
		public bool ExtTextOut(int x, int y, string text)
		{
			return Win32.ExtTextOut(m_hdc, x, y, 0, IntPtr.Zero, text, 
			                        Convert.ToUInt16(text.Length), IntPtr.Zero);
		}

		public bool ExtTextOut(int x, int y, int width, string text)
		{
			Size size = GetTextExtent(text);
			return ExtTextOut(x + (width - size.Width) / 2, y, text);
		}

		//
		// FillRect
		//
		public int FillRect(Win32.RECT rect, BrushGdi hbrush)
		{
			return Win32.FillRect(m_hdc, ref rect, (IntPtr) hbrush);
		}

		public int FillRect(int x, int y, int w, int h, BrushGdi hbrush)
		{
			Win32.RECT rect = new Win32.RECT(x, y, w, h);
			return Win32.FillRect(m_hdc, ref rect, (IntPtr) hbrush);
		}

		public int FillRect(Rectangle rect, BrushGdi hbrush)
		{
			return FillRect(new Win32.RECT(rect), hbrush);
		}

		public int FillRect(Rectangle rect, Color color)
		{
			using (BrushGdi br = CreateSolidBrush(color))
			{
				return FillRect(rect, br);
			}
		}

		public bool RoundRect(int left, int top, int right, int bottom, int roundW, int roundH)
		{
			return Win32.RoundRect(m_hdc, left, top, right, bottom, roundW, roundH);
		}

		//
		// FrameRect
		//
		public void FrameRect(Rectangle rect)
		{
			BrushGdi brOld = Brush;
			Brush = GetStockBrush(Win32.StockObjects.NullBrush);
			DrawRectangle(rect);
			Brush = brOld;
		}

		//
		// Font Property
		//
		public FontGdi Font
		{
			set
			{
				if (value.IsEmpty && m_fontChanged)
				{
					Win32.SelectObject(m_hdc, (IntPtr) m_oldFont);
					m_oldFont = IntPtr.Zero;
					m_font = FontGdi.Empty;
				}

				IntPtr oldFont = Win32.SelectObject(m_hdc, (IntPtr) value);
				if (!m_fontChanged)
				{
					m_oldFont = oldFont;
					m_fontChanged = true;
					m_font = value;
				}
			}
			get { return m_font; }
		}

		//
		// GetStockBrush
		//
		public BrushGdi GetStockBrush(Win32.StockObjects type)
		{
			return new BrushGdi(Win32.GetStockObject(type), true);
		}

		//
		// GetTextExtent
		//
		public Size GetTextExtent(string text)
		{
			if (text == null || text.Length == 0)
				return Size.Empty;

			Size size = Size.Empty;
			int fit;
			Win32.GetTextExtentExPoint(m_hdc, text, text.Length, 10000, out fit, null, ref size);
			return size;
		}

		//
		// GetTextExtent
		//
		// On Entry:
		//		text	The text that we want to measure
		//		width	The maximum width that we'll allow
		//
		// On Exit:
		//		extents	An array of character positions for the string
		//		maxFit	The maximum number of characters that will fit in width.
		//
		// Returns:
		//		size	The width and height of the string.
		//
		public Size GetTextExtent(string text, int width, out int[] extents, out int maxFit)
		{
			Size size = Size.Empty;
			extents = new int[text.Length];
			Win32.GetTextExtentExPoint(m_hdc, text, text.Length, width, out maxFit, extents, ref size);
			return size;
		}

		//
		// GetDrawTextSize
		//
		public Size GetDrawTextSize(string text, int width, Win32.DT format)
		{
			Win32.RECT rect = new Win32.RECT(0, 0, width, 1);
			Win32.DrawText(m_hdc, text, text.Length, ref rect, format | Win32.DT.CALCRECT);

			return new Size(rect.Width, rect.Height);
		}

		public bool DrawLine(int x1, int y1, int x2, int y2)
		{
			Point[] points = new Point[2];
			points[0] = new Point(x1, y1);
			points[1] = new Point(x2, y2);
			return Polyline(points);
		}

		public void DrawLine(int x1, int y1, int x2, int y2, Color color)
		{
			PenGdi pen = CreatePen(Color.Gray);
			PenGdi oldPen = SelectObject(pen);

			DrawLine(x1, y1, x2, y2);

			SelectObject(oldPen);
			pen.Dispose();
		}

		public void DrawRectangle(Rectangle rect)
		{
			Win32.Rectangle(m_hdc, rect.Left, rect.Top, rect.Right, rect.Bottom);
		}



		//
		// Pen property
		//
		// Note: This method does not dispose of the pen that you created. You'll need
		//		 to do that yourself after you've finished using it.
		//
		//	Value:	PenGdi			The pen that you want to use for drawing
		//			PenGdi.Empty	Release the last pen from this DC.
		//
		public PenGdi Pen
		{
			set
			{
				if (value.IsEmpty())
				{
					if (m_oldPen != IntPtr.Zero)
						Win32.SelectObject(m_hdc, m_oldPen);
					m_oldPen = IntPtr.Zero;
					m_pen = null;
					return;
				}

				IntPtr oldPen = Win32.SelectObject(m_hdc, (IntPtr) value);
				if (m_oldPen == IntPtr.Zero)
					m_oldPen = oldPen;
				m_pen = value;
			}
		}

		//
		// Polygon
		//
		public bool Polygon(Point[] points)
		{
			return Win32.Polygon(m_hdc, points, Convert.ToInt16(points.Length));
		}

		//
		// Polyline
		//
		public bool Polyline(Point[] points)
		{
			return Win32.Polyline(m_hdc, points, Convert.ToInt16(points.Length));
		}

		//
		// SelectObject Method
		//
		// This method includes overloads for the type-safe objects that can be
		// selected into this DC.
		//
		public IntPtr SelectObject(IntPtr obj)
		{
			return Win32.SelectObject(m_hdc, obj);
		}

		public PenGdi SelectObject(PenGdi pen)
		{
			return Win32.SelectObject(m_hdc, (IntPtr) pen);
		}

		public BrushGdi SelectObject(BrushGdi brush)
		{
			return Win32.SelectObject(m_hdc, (IntPtr) brush);
		}

		//
		// TextColor Property
		//
		public Color TextColor
		{
			get { return m_textColor; }
			set
			{
				if (value == m_textColor)
					return;

				int color = (value.B << 16) + (value.G << 8) + value.R;
				Win32.SetTextColor(m_hdc, color);
				m_textColor = value;
			}
		}

		public Win32.TextAlign TextAlign
		{
			get
			{
				return m_TextAlign;
			}
			set
			{
				if(m_TextAlign == value)
					return;

				Win32.SetTextAlign(m_hdc, (uint)value);
				m_TextAlign = value;
			}
		}


		//
		// Transparent
		//
		// Control whether text drawing is transparent or opaque.
		//
		public bool Transparent
		{
			set
			{
				Win32.BkgMode mode = (value) ? Win32.BkgMode.TRANSPARENT : Win32.BkgMode.OPAQUE;
				Win32.SetBkMode(m_hdc, mode);
			}
		}

		//
		// Release all of our unmanaged resources.
		//
		private void Release()
		{
			if (m_hdc != IntPtr.Zero)
			{
				if (m_fontChanged)
					Win32.SelectObject(m_hdc, m_oldFont);
				if (m_oldPen != IntPtr.Zero)
					Win32.SelectObject(m_hdc, m_oldPen);

				if (m_ownDc)
					Win32.ReleaseDC(m_hwnd, m_hdc);
				m_hdc = IntPtr.Zero;
			}
		}

		public enum PenMode : int
		{
			Solid	= 0,
			Dash	= 1,
			Null	= 5,
		}

		public bool BitBlt(int nxDest, int dyDest, int nWidth, int nHeight, Gdi src, int nxSrc, int nySrc, Win32.ERop rop)
		{
			return Win32.BitBlt(m_hdc, nxDest, dyDest, nWidth, nHeight, src.m_hdc, nxSrc, nySrc, rop);
		}

		public bool BitBlt(int nxDest, int dyDest, int nWidth, int nHeight, IntPtr hdcSrc, int nxSrc, int nySrc, Win32.ERop rop)
		{
			return Win32.BitBlt(m_hdc, nxDest, dyDest, nWidth, nHeight, hdcSrc, nxSrc, nySrc, rop);
		}

		public bool StretchBlt(int nxDest, int dyDest, int nWidth, int nHeight, Gdi src, int nxSrc, int nySrc, int nSWidth, int nSHeight, Win32.ERop rop)
		{
			return Win32.StretchBlt(m_hdc, nxDest, dyDest, nWidth, nHeight, src.m_hdc, nxSrc, nySrc, nSWidth, nSHeight, rop);
		}

		public bool StretchBlt(int nxDest, int dyDest, int nWidth, int nHeight, IntPtr hdcSrc, int nxSrc, int nySrc, int nSWidth,int nSHeight, Win32.ERop rop)
		{
			return Win32.StretchBlt(m_hdc, nxDest, dyDest, nWidth, nHeight, hdcSrc, nxSrc, nySrc,nSWidth,nSHeight, rop);
		}

		public Int32 BltAlpha(int left, int top, int width, int height, Gdi source, byte transp)
		{
			var blendFunction = new Win32.BlendFunction
			                    	{
			                    		BlendOp = ((byte) Win32.BlendOperation.AC_SRC_OVER),
			                    		BlendFlags = 0,
			                    		SourceConstantAlpha = transp,
			                    		AlphaFormat = 0
			                    	};

			Int32 rv = Win32.AlphaBlend(m_hdc, left, top, width, height, source.m_hdc, 0, 0, width, height,
			                 blendFunction);

			return rv;
		}

		public bool TransparentImage(int xDestOrg,int  yDestOrg, int dxDest, int dyDest, IntPtr hdcSrc, int xSrcOrg, int ySrcOrg, int dxSrc, int dySrc, Color color)
		{
			return Win32.TransparentImage(m_hdc, xDestOrg, yDestOrg, dxDest, dyDest, hdcSrc, xSrcOrg, ySrcOrg, dxSrc, dySrc,
			                       Win32.Color2ColorRef(color));
		}



		public enum FillDirection
		{
			LeftToRight = Win32.GRADIENT_FILL_RECT_H,
			TopToBottom = Win32.GRADIENT_FILL_RECT_V
		}

		public bool GradientFill(Rectangle rc, Color startColor, Color endColor, FillDirection fillDir)
		{
			var tva = new Win32.TRIVERTEX[2];
			tva[0] = new Win32.TRIVERTEX(rc.X, rc.Y, startColor);
			tva[1] = new Win32.TRIVERTEX(rc.Right, rc.Bottom, endColor);
			var gra = new [] {new Win32.GRADIENT_RECT(0, 1)};

			return  Win32.GradientFill(m_hdc, tva,(uint)tva.Length, gra, (uint)gra.Length, (uint)fillDir);
		}

		public IntPtr CreateCompatibleDC()
		{
			return Win32.CreateCompatibleDC(m_hdc);
		}

		public static void ReleaseDC(IntPtr hdc)
		{
			Win32.DeleteDC(hdc);
		}

		public static Bitmap CreateControlSnapshot(Control control)
		{
			Bitmap bmp = null;

			try
			{
				using (Graphics gx = control.CreateGraphics())
				{
					Rectangle rect = control.Bounds;
					bmp = new Bitmap(rect.Width, rect.Height);

					using (var gxComp = Graphics.FromImage(bmp))
					{
						var hdcBmp = gxComp.GetHdc();
						var hdcControl = gx.GetHdc();

						using (var g = FromHdc(hdcBmp, Rectangle.Empty))
						{
							g.BitBlt(0, 0, rect.Width, rect.Height, hdcControl, rect.Left, rect.Top,
							                    Win32.ERop.SRCCOPY);
						}

						gxComp.ReleaseHdc(hdcBmp);
						gx.ReleaseHdc(hdcControl);
					}

					return bmp;
				}
			}
			catch
			{
				if(bmp != null)
					bmp.Dispose();

				return null;
			}
		}
	}
}